#include "FileSystem.h"

FileSystem::FileSystem(string& fileName, unsigned int blockSize, unsigned int diskSize, unsigned int iNodesCount) :
	_disk(fileName,blockSize,diskSize,iNodesCount) {
}
FileSystem::~FileSystem() {
}

INodeId FileSystem::createDir() {
	// INodeId dirINode = this->_disk.allocateInode();
	// this->_disk.setInodeType(dirINode,DIRECTORY_TYPE);
	// this->_disk.setFileSize(dirINode,0);
	// return dirINode;
	return createFile(DIRECTORY_TYPE);
}

INodeId FileSystem::createFile(INodeType ft) {
	INodeId dirINode = this->_disk.allocateInode();
	this->_disk.setInodeType(dirINode, ft);
	this->_disk.setFileSize(dirINode, 0);
	return dirINode;
}
INodeType FileSystem::getFileType(INodeId inode) {
	return this->_disk.getInodeType(inode);
}
//@pre: bytes<buffer

void FileSystem::f_read(INodeId inode, buffer buf, offset off, bytes nBytes) {
	unsigned int blockSize = _disk.getBlockSize();
	unsigned int whichBlock = off / blockSize;
	unsigned int postionInBlock = off % blockSize;
//	cout<<"offset:" << off<<" nBytes: "<< nBytes<<"indoe: "<<inode<<"buffer :"<<buf<<endl;
	fragmentBlockAllocation(inode,((nBytes+off)/blockSize)+1);
	char* blockData = new char[blockSize];
	_disk.readBlock( _disk.getDataBlock(inode, whichBlock), blockData);
	for (unsigned int i =0; postionInBlock+i<blockSize && nBytes >0; i++) {
		char c;
		c = blockData[i+postionInBlock];
		buf[i]=c;
		nBytes --;
	}
	if (nBytes > 0)
		f_readrec(inode, buf+blockSize-postionInBlock, off+blockSize
				-postionInBlock, nBytes);
//	delete blockData;
//	cout<<"end f_read"<<endl;
}
void FileSystem::f_readrec(INodeId inode,buffer buf,offset off,bytes nBytes){
	unsigned int blockSize = _disk.getBlockSize();
	unsigned int whichBlock = off / blockSize;
	char* blockData = new char[blockSize];
	_disk.readBlock(_disk.getDataBlock(inode, whichBlock), blockData);
	for (unsigned int i =0; i<blockSize && nBytes >0; i++) {
		char c;
		c = blockData[i];
		buf[i]=c;
		nBytes --;
	}
	if (nBytes > 0)
		f_readrec(inode, buf+blockSize, off+blockSize, nBytes);
}
void FileSystem::f_write(INodeId inode, buffer dataInput, offset at, bytes nBytes) {
//	cout<<"#@$@## "<< nBytes<<endl;
	unsigned int blockSize = _disk.getBlockSize();
	unsigned int whichBlock = at / blockSize;
	unsigned int postionInBlock = at % blockSize;
	fragmentBlockAllocation(inode,((nBytes+at)/blockSize) + 1);
	char* blockData = new char[blockSize];
	_disk.readBlock(_disk.getDataBlock(inode, whichBlock), blockData);
	for (unsigned int i =0; postionInBlock+i<blockSize && nBytes >0; i++) {
		blockData[i+postionInBlock]=dataInput[i];
		nBytes --;
	}
	_disk.writeBlock(_disk.getDataBlock(inode, whichBlock), blockData);
	if (nBytes > 0)
		f_write(inode, dataInput+blockSize-postionInBlock, at+blockSize-postionInBlock, nBytes);
	delete blockData;
	//same idea as read block , only call diffrent system call. writeBlock and create blocks in each
	//recursive cals
}

void FileSystem::f_writerec(INodeId inode,buffer buf,offset off,bytes nBytes){
	cout<<"#@$@## "<< _disk .getBlockSize()<<endl;
	unsigned int blockSize = _disk.getBlockSize();
	unsigned int whichBlock = off / blockSize;
	char* blockData = new char[blockSize];
	_disk.writeBlock(_disk.getDataBlock(inode, whichBlock), blockData);
	for (unsigned int i =0; i<blockSize && nBytes >0; i++) {
		char c;
		c = buf[i];
		blockData[i]=c;
		nBytes --;
	}
	_disk.writeBlock(_disk.getDataBlock(inode, whichBlock), blockData);
	if (nBytes > 0)
		f_writerec(inode, buf+blockSize, off+blockSize, nBytes);
}
void FileSystem::bufferToFileHeader(FileHeader& fh, char* buf) {
	strncpy(fh.fileName,buf,12);
	char tmp[5];
	strncpy(tmp,buf+12,4);
	tmp[4]='\0';
	fh.iNodeNumber = atoi(tmp);
}
void FileSystem::fileHeaderToBuffer(char* buf,FileHeader fh) {
	strncpy(buf,fh.fileName,12);
	std::string s;
	std::stringstream out;
	out << fh.iNodeNumber;
	const char* tmp = out.str().c_str();
	strncpy(buf+12,tmp,4);
}
//@post: the caller should release the list.
std::vector<FileHeader>* FileSystem::d_read(INodeId inode) {
	vector<FileHeader>* data=new vector<FileHeader>();
	unsigned int nBytes = _disk.getFileSize(inode);
	unsigned int BlockSize = _disk.getBlockSize();

	char* buf = new char[nBytes];
	this->f_read(inode,buf,0,nBytes);
	for (unsigned int i=0; i<(nBytes/sizeof(FileHeader)); i++) {
		FileHeader* fh = new FileHeader;
		bufferToFileHeader(*fh, buf+(i*16));
		data->push_back(*fh);
	}
		delete buf;
	//for each block reference from iNode
	//read blcok
	//iterate on data in block
	//add data to list
	return data;
}

void FileSystem::d_write(INodeId inode, vector<FileHeader>* data) {
	unsigned int tBlockSize = _disk.getBlockSize();
	unsigned int tListSize = sizeof(FileHeader) * data->size();//in bytes
	unsigned int tAmountOfBlocks =tListSize/tBlockSize;
	if (tListSize % tBlockSize)
		tAmountOfBlocks++;


	char* tBuf = new char[tListSize];
	listToBuffer(*data,tBuf,tListSize);
	this->f_write(inode,tBuf,0,tListSize);
	this->_disk.setFileSize(inode,tListSize);
//	delete tBuf;
}
void FileSystem::listToBuffer(vector<FileHeader> data,char* buf,unsigned int listSize){
	vector<FileHeader>::iterator iter = data.begin();
	for(unsigned int i=0;i<data.size()&& data.end()!=iter;i++){
		fileHeaderToBuffer(buf+(i*sizeof(FileHeader)),*iter);
		iter++;
	}
}
void FileSystem::f_delete(INodeId fileINode) {
	_disk.freeInode(fileINode);
}

void FileSystem::d_delete(INodeId folderINode) {
	_disk.freeInode(folderINode);
}

INodeId FileSystem::getRootINode(){
	return _disk.getRootINode();
}

void FileSystem::fragmentBlockAllocation(INodeId inode,BlockId blockNumberInInode){
	if (blockNumberInInode>0){
		if ( NULL == _disk.getDataBlock(inode,blockNumberInInode))
			_disk.setDataBlock(inode,blockNumberInInode,_disk.allocateDataBlock());
		fragmentBlockAllocation(inode,blockNumberInInode-1);
	}
}

